local cjson = require "cjson"
local http = require "resty.http"
local StringUtil = require "util.StringUtil"
local log = require "core.log"
local yaml = require("lualib.tinyyaml")

local CacheUtil = require("util.CacheUtil")
local PropertiesUtil = require "util.PropertiesUtil"
-- consul watcher
-- 设置key
-- curl -X PUT http://172.20.20.10:8500/v1/kv/broker/kafka/172.20.20.11:8080
--
-- 获取所有前缀key
-- curl http://172.20.20.10:8500/v1/kv/broker/kafka/?recurse
-- [{"LockIndex":0,"Key":"broker/kafka/172.20.20.11:8080","Flags":0,"Value":null,"CreateIndex":34610,"ModifyIndex":34610}]
--
-- 获取所有key,index大于34610版本号(当有多个key时需要获取最大版本号)
-- 没有更新,consul阻塞5分钟
-- curl "http://172.20.20.10:8500/v1/kv/broker/kafka/?recurse&index=34610"

local cache = {}

setmetatable(cache, { __mode="kv"} )

local DEFAULT_TIMEOUT = 6 * 60 * 1000 -- consul默认超时5分钟

local _M = {}

local mt = { __index = _M }

--- 继承
function _M.new(self, watch_url, callback)
    local watch = cache[watch_url]  -----观察者
    if watch ~= nil then   ---
        return watch
    end

    local httpc, err = http.new()  --创建http连接
    if not httpc then
        return nil, err
    end
    httpc:set_timeout(DEFAULT_TIMEOUT)   ---设置超时

    local recurse_url = watch_url .. "?recurse&wait=5m"
    watch =  setmetatable({
        httpc = httpc,
        recurse_url = recurse_url,
        modify_index = 0,
        running = false,
        stop = false,
        callback = callback,
    }, mt)

    cache[watch_url] = watch   ------缓存的url 和watch 的关系
    return watch
end


----开始运转
function _M.start(self)     ---开始跑
---如果正在跑的话 报错
---
    if self.running then   ----如果正在跑 就不需要再跑了  防止重复启动线程
        ngx.log(ngx.ERR, "watch already start, url:", self.recurse_url)
        return
    end

    local is_exiting = ngx.worker.exiting   ---判断当前线程是否退出

    ---一直重复  定义重复执行函数
    local watch_index= function()
        repeat
            --找到之前的index
            local prev_index = self.modify_index
            --找到等待的url  阻塞的url
            local wait_url = self.recurse_url .. "&index=" .. prev_index
            ngx.log(ngx.ERR, "wait:", wait_url)
            ---请求并阻塞 如果没有更新的话
            --- 这里需要增加一个等待 否则会重复一直快速刷
            local result = self:request(wait_url)   ----http请求
            if result then
                self:get_modify_index(result)   ----获取请求的结果的 modify _index
                if self.modify_index > prev_index then -- modify_index change
                    ngx.log(ngx.ERR, "watch,url:", self.recurse_url, " index change")   ---
                    self:callback(result)   ----- 执行 callback
                end
            end

        until self.stop or is_exiting()  --直到 停止 或者work 停止退出
        ngx.log(ngx.ERR, "watch exit, url: ", self.recurse_url)
    end

    local ok, err = ngx.timer.at(1, watch_index)   ---1s后执行函数
    if not ok then
        ngx.log(ngx.ERR, "failed to create watch timer: ", err)
        return
    end
    self.running = true
end


function _M.stop(self)
    self.stop = true
    ngx.log(ngx.ERR, "watch stop, url:", self.recurse_url)
end

---获取最大的修改index
function _M.get_modify_index(self, result)
    local key = "ModifyIndex"
    local max_index = self.modify_index
    for _, v in ipairs(result) do   ---返回的所有key 里都是一个json 吗 返回的都是一个数组 需要遍历 拿到最大的index
        local index = v[key]
        if index > max_index then
            max_index = index
        end
    end
    self.modify_index = max_index
end

--发送请求
function _M.request(self, url)
    local res, err = self.httpc:request_uri(url)
    if not res then
        ngx.log(ngx.ERR, "watch request error, url:", url, " error:", err)
        return nil, err
    end
    return json.decode(res.body)
end
---从指定的consul 上获取配置信息
function _M. get_Config_from_consul(consul_ip,consul_port,config_consul_path,data_center)

    local http = require "resty.http"
    local httpc = http:new()
    --生产10.42.28.27

    local config_data ={};
    -- local consul_host = local_conf.openresty.cloud.consul.host;
    local consul_host =consul_ip;
    local consul_port = consul_port;
    --  local consul_port = local_conf.openresty.cloud.consul.port or 8500;

    --如果不是ip地址还要转换成ip地址
    if  not StringUtil.isIp(consul_host) then
        local realIp,domain =  HttpUtil:_get_addr(consul_host) --192.168.212.74
        if realIp ==nil then
            log.error("没有解析出consul的ip地址");
            return
        end
        consul_host = realIp;

    end
    --if(StringUtil)


    log.info("-------找到domain的值-------"..consul_host)
   -- local consul_config = local_conf.openresty.cloud.consul.config;
    print("cosul路径".."http://"..consul_host..":"..consul_port.."/v1/kv/config/"..config_consul_path.."?dc="..data_center)

    local resp,err = httpc:request_uri("http://"..consul_host..":"..consul_port.."/", ----请求http 接口

            {
                method = "GET",    ---请求方式
            ---             http://192.168.213.22:8500/v1/kv/config/smarthome/smarthome-app-srv/smarthome-app-srv-dev.yml?dc=data-center-1
            path="/v1/kv/config/"..config_consul_path.."?dc="..data_center,--consul上对应的服务名称 alpha是data-center-1
                headers = {
                    ["Content-Type"] = "application/json",
                }
            })

    if not resp then
        log.error("请求consul 失败!!!!!!!!!!!!!!!")

        return
    end
    if not resp.body then
        log.error("请求consul 失败!!!!!!!!!!!!!!")

        return
    end
    resp=resp.body
    --log.info("请求consul 失败!!!!!!!!!!!!!!")
    --log.info("请求http请求返回结果为"..resp)
    ngx.log(ngx.INFO,"asdfasdf")
    print("请求http请求返回结果为"..resp)
    --log.info("请求http请求返回结果为"..cjson.encode(resp))
    --local resp = cjson.decode(resp);
    resp = cjson.decode(resp)  ---这里也可能因为consul的关系没有返回正确的值
    log.info("找到的结果cjson后数据类型是"..type(resp))

    local upstreams = {}
    for i, v in ipairs(resp) do
        log.info("找到的结果是["..tostring(i).."]value:"..type(v))
        --ngx.log(ngx.INFO,"找到的结果是["..tostring(i).."]value:"..v.key ..v.Value)
        -- ngx.say(v["Key"] ..ngx.decode_base64(v["Value"]))
        -- ngx.say("拿到的properties 是",ngx.decode_base64(v["Value"]))
        ----开始解析这个properties
        local propertiesStr =ngx.decode_base64(v["Value"]);---base64转码获取得到对应的配置str
        print(propertiesStr);

        ---需要判断是properties 还是 yaml

        if StringUtil.endswith(StringUtil,config_consul_path,"properties") then
            log.info("发现是properties");
            config_data = PropertiesUtil.parseProperties(propertiesStr); --将字符串解析到table
        elseif StringUtil.endswith(StringUtil,config_consul_path,"yaml") or  StringUtil.endswith(config_consul_path,"yml")   then
            log.info("发现是yaml");
            config_data = yaml.parse(propertiesStr); --将字符串解析到table

        end



        --ngx.log(ngx.ERR,"find consul service address"..v.ServiceAddress)
        --upstreams[i] = {ip=v.ServiceAddress, port=3000}
        for k,v in pairs(config_data) do  ---遍历这个table  然后把值放到 shared中
        if type(v) == "string" then
           -- ngx.shared.properties:set(k, v)
            --放到整个的shared dict 字典里

            end
        end
    end
    --printTable(local_conf,4);--打印table
    return config_data;
end


--  consul的ip地址 和端口 要注册的服务id 和服务名称 ,以及服务的ip和端口
--[[
consul_ip: 192.168.213.91
consul_port: 8500
_serverId: apiservice6  服务的id
_serverName: apiservice6 服务的名称
_ip, 本服务的ip
_port, 本服务的端口
healthUrl 健康检查的url
]]--
function _M.registerServer(consul_ip,consul_port,_serverId,_serverName,_ip,_port,healthUrl)

    local post_json = {ID= _serverId,
                       Name=_serverName,
                       Address= _ip,
                       Tags={ "webapi"},
                       Port= _port,
        ---检查的信息
        check={
           --Args="/usr/local/bin/check_redis.py",
            http="http://".._ip..":".._port.."/health",
            id=_serverName.."check",
            name =_serverName.."check",
            tls_server_name= "",
            tls_skip_verify=false,
            method="GET",
            --header={"Content-Type"={ "application/json"}},
            --body="{\"method\":\"health\"}",
            interval="1m",
            timeout="1s"
        }
    };
    local json_str = cjson.encode(post_json)
    ----此接口用来注册服务并替换已经存在的服务
    print("注册url为  ".."http://"..consul_ip..":"..consul_port.."/".."/v1/agent/service/register?replace-existing-checks=true");
    print("注册json 为 "..json_str);
    local httpc, err = http.new()  --创建http连接
    if not httpc then
        return nil, err
    end
    httpc:set_timeout(DEFAULT_TIMEOUT)   ---设置超时
    local resp,err = httpc:request_uri("http://"..consul_ip..":"..consul_port.."/", ----请求http 接口
            {
                method = "put",    ---请求方式
                  path="/v1/agent/service/register?replace-existing-checks=true",
                body=json_str,
                headers = {
                    ["Content-Type"] = "application/json",
                }
            })
    if not resp then
        log.error("请求consul 失败!!!!!!!!!!!!!!!")

        return
    end
    if not resp.body then
        log.error("请求consul 失败!!!!!!!!!!!!!!")

        return
    end
    print("请求http请求返回结果为"..resp.status)
    if resp.status==200 then
        print(" 注册成功");
    end
end


---获取所有的服务名称相关的 节点
function _M.getServiceNodes(consul_ip,consul_port,serviceName)

    local http = require "resty.http"
    local httpc = http:new()
    --生产10.42.28.27
    print("开始请求".."http://"..consul_ip..":"..consul_port  .."/v1/catalog/service/"..serviceName )
    local resp,err = httpc:request_uri("http://"..consul_ip..":"..consul_port,
            {
                method = "GET",    ---请求方式
            path="/v1/catalog/service/"..serviceName,--consul上对应的服务名称
                --path="/v1/catalog/service/smarthome-app-srv",

                --path="/search_product.htm?q=ipone",
                --query="q=iphone",    ---get方式传参数
                --body="name='jack'&age=18",    ---post方式传参数
                --path="/search_product.htm",    ----路径
                ---header参数
                --headers = {["User-Agent"]="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36"}
                headers = {
                    ["Content-Type"] = "application/x-www-form-urlencoded",
                }
            })
    if not resp then
        print("请求consul 失败!!!!!!!!!!!!!!!")
        return
    end
    if not resp.body then
        print("请求consul 失败!!!!!!!!!!!!!!!")
        return
    end
    resp=resp.body
    print("请求http请求返回结果为",resp)
    --ngx.log("请求http请求返回结果为"..cjson.encode(resp))
    --local resp = cjson.decode(resp);
    resp = cjson.decode(resp)
    local upstreams = {}
    for i, v in ipairs(resp) do
        ngx.log(ngx.INFO,"find consul service address"..v.ServiceAddress.."端口是"..v.ServicePort)
        upstreams[i] = {ip=v.ServiceAddress, port=v.ServicePort}

    end
    ---将服务节点 缓存到 缓存当中 以准备让 balacer 阶段直接取用这里的数据
    CacheUtil.set("upstreams_"..serviceName,upstreams);
    return upstreams ,nil
    --ngx.shared.upstream_list:set("api_list", cjson.encode(upstreams))

end
--[[
  balance_by_lua used in upstream directive

--]]

----通过服务名称进行负载均衡
function _M.balancer_by_server_name(self ,ip,port,serverName)


    local CacheUtil = require "util.CacheUtil"
    --从缓存中获取是否有对应的服务名的节点
    local myupstream =      CacheUtil:get("upstream"+serverName);

    if myupstream ==nil then
        -----不能被balance 使用
            ngx.log(ngx.ERR,"未被初始化服务名: "..serverName )

        return 301, "未被初始化服务名: ";
      --  myupstream = self.getServiceNodes(ip,port,serverName);
    end

    if myupstream==nil or #myupstream ==0 then
        return 301,"返回失败"
    end
    -----
    local balancer = require "ngx.balancer";

    local ip_port = myupstream[math.random(1, table.getn(myupstream))];

    print("balancer 到 ip"..ip_port.ip.."ip_port.port"..tostring(ip_port.port))
    balancer.set_current_peer(ip_port.ip, ip_port.port);

end


return _M



